#include "Engine.hpp"
#include "glad/glad.h"
#include "gl/Stencil.hpp"
#include "Objects/flecs_components.hpp"

#include <glm/gtx/quaternion.hpp>
#include <glm/ext/quaternion_common.hpp>

Engine::Engine(GLFWwindow* window, const char* work_directory)
{
	num_shaders = -1;
	prefix = work_directory;
	
	world_ecs.entity("invisible");
	addShaderProgram("core_base",  prefix + DEFAULT_VERT, prefix + DEFAULT_FRAG);
	addShaderProgram("core_light", prefix + DEFAULT_VERT, prefix + DEFAULT_FRAG_LIGHT);

	model_mat4_querys.resize(2);

	num_omni = static_cast<uint8_t>(num_spot = time = prev_time = period = 0.f);

	camera = new Camera(window, world_ecs, glm::vec3(0.f, 3.f, 17.f), glm::vec3(0.f, 0.f, -1.f), 6.f, 50.f, .005f);
}
void Engine::draw()
{
	time = static_cast<float>(glfwGetTime());

	Stencil::on();
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | Stencil::buffer);
	Stencil::off();


	shader_query.each([this](flecs::entity e, Shader& shader, uint16_t& num_shader)
	{
		shader.use();
		shader.setVec3("viewPos", camera->getPosition());
		shader.setMat4("u_VPmatrix", camera->getMatrix());

		model_mat4_querys[num_shader].each([this, shader](Model& model, mat4& matrix)
		{	
			glm::mat3 nm_matrix = glm::transpose(glm::inverse(glm::mat3(matrix)));

			shader.setMat4("u_Mmatrix", matrix);
			shader.setMat3("u_NMmatrix", nm_matrix);

			model.draw();
		});
	});


	camera->process(period);

	period = time - prev_time;
	prev_time = time;
}
//		Stencil::on();
//		Stencil::func(Stencil::always, 1);
//		draw_object(highlighted_object);
//
//		Stencil::off();
//		glDisable(GL_DEPTH_TEST);
//		Stencil::func(Stencil::if_equal, 0);
//
//		coreShaderProgMark->use();
//		draw_highlighted_object(highlighted_object);
//		glEnable(GL_DEPTH_TEST);
//		coreShaderProg.use();
//	}
//void Engine::draw_object(Object*& object)
//{
//	for (uint8_t i = 0; i < object->model*->texture_cache.size(); i++)
//	{
//		glActiveTexture(GL_TEXTURE0 + i);
//		glBindTexture(GL_TEXTURE_2D, object->model*->texture_cache[i]->ID);
//		coreShaderProg.setInt(object->model*->texture_cache[i]->type, i);
//	}
//	Manager::setUniform(object, coreShaderProg, camera);
//	object->model*->draw();
//}
//void Engine::draw_highlighted_object(Object*& object)
//{
//	glm::mat4 model(object->matrix);
//	object->matrix = glm::scale(object->matrix, glm::vec3(1.02f));
//	object->matrix = glm::translate(object->matrix, glm::vec3(0.0f, 0.0f, 0.04f));
//
//	Manager::setUniform(object, coreShaderProgMark, camera);
//	object->model*->draw();
//
//	object->matrix = model;
//	//object->model = glm::scale(object->model, glm::vec3(1 / 1.02f));
//}




Object3D* Engine::addObject3D(std::string name, std::string modelPath)
{
	model_mat4_querys[0] = world_ecs.query_builder<Model, mat4>()
		.term(world_ecs.lookup("core_base"))
		.build();
	
	return new Object3D(name, world_ecs, world_ecs.lookup("core_base"), prefix + modelPath);
}
OmniLight3D* Engine::addOmniLight3D(std::string name, float power, const vec3& color, std::string modelPath)
{
	world_ecs.lookup("core_light").get<Shader>()->use();
	world_ecs.lookup("core_light").get<Shader>()->setVec3("color", color);

	model_mat4_querys[1] = world_ecs.query_builder<Model, mat4>()
		.term(world_ecs.lookup("core_light"))
		.build();

	if (modelPath != "")
	{
		return new OmniLight3D(name, world_ecs, world_ecs.lookup("core_light"), ++num_omni,
							   world_ecs.lookup("core_base").get<Shader>(), color, power, prefix + modelPath);
	} else {
		return new OmniLight3D(name, world_ecs, world_ecs.lookup("invisible"), ++num_omni,
							   world_ecs.lookup("core_base").get<Shader>(), color, power);
	}
}
SpotLight3D* Engine::addSpotlight3D(std::string name, glm::vec2 cutoff_and_power, const vec3& color, std::string modelPath)
{
	world_ecs.lookup("core_light").get<Shader>()->use();
	world_ecs.lookup("core_light").get<Shader>()->setVec3("color", color);

	model_mat4_querys[1] = world_ecs.query_builder<Model, mat4>()
		.term(world_ecs.lookup("core_light"))
		.build();

	if (modelPath != "")
	{
		return new SpotLight3D(name, world_ecs, world_ecs.lookup("core_light"), ++num_spot,
							   world_ecs.lookup("core_base").get<Shader>(), color,
							   cutoff_and_power.x, cutoff_and_power.y, prefix + modelPath);
	} else {
		return new SpotLight3D(name, world_ecs, world_ecs.lookup("invisible"), ++num_spot,
							   world_ecs.lookup("core_base").get<Shader>(), color,
							   cutoff_and_power.x, cutoff_and_power.y);
	}
}
const Shader* Engine::addShaderProgram(std::string name, std::string vs, std::string fs)
{
	auto entity = world_ecs
		.entity(name.c_str())
		.set<Shader>(Shader(vs, fs))
		.set<uint16_t>(++num_shaders);
	
	shader_query = world_ecs.query<Shader, uint16_t>();

	return entity.get<Shader>();
}

//void Engine::Highlight(std::string name)
//{
//	const auto& model = ecs.lookup(name.c_str()).get<Model*>();;
//	if (highlighted_object != model)
//	{
//		highlighted_object = objects[object_index];
//	}
//	else
//	{
//		highlighted_object = nullptr;
//	}
//}